using System;
using System.Collections.Generic;
using System.Text;
using NGinnBPM.Services;
using NLog;
using System.IO;
using System.Xml;
using NGinnBPM.Lib.Data;
using NGinnBPM.Lib.Schema;
using NGinnBPM.Lib.Interfaces;
using NGinnBPM.Services.Persistence;
using NGinnBPM.Runtime;
using NHibernate;


namespace NGinnBPM.Dao
{ 

    /// <summary>
    /// Implementation of process instance repository storing processes in a SQL database.
    /// Custom NGinn serialization is used and processes are stored as XML.
    /// </summary>
    public class TaskInstanceHibernator : ITaskInstanceRepository
    {

        private static Logger log = LogManager.GetCurrentClassLogger();
        private ISessionFactory _dbSessionFactory;
        
        
        public ISessionFactory SessionFactory
        {
            get { return _dbSessionFactory; }
            set { _dbSessionFactory = value; }
        }


        public TaskInstanceHibernator()
        {
            
        }

        
        

        protected virtual DataObject GetTaskState(string instanceId)
        {
            using (ISession sess = SessionFactory.OpenSession())
            {
                ICriteria crit = sess.CreateCriteria(typeof(TaskInstanceData));
                crit.Add(NHibernate.Criterion.Expression.Eq("id", instanceId));

                //IQuery qq = sess.CreateQuery("from TaskInstanceData p where p.id = :instId");
                //qq.SetString("instId", instanceId);
                IList<TaskInstanceData> lst = crit.List<TaskInstanceData>(); //qq.List<TaskInstanceData>();
                if (lst.Count == 0) return null;
                DataObject dob = DataObject.ParseXml(lst[0].TaskData);
                return dob;
            }
        }

        

        
        
        #region IProcessInstanceRepository Members
 
       

        

       





        public IList<string> SelectProcessesWithReadyTokens()
        {
            List<string> s = new List<string>();
            using (ISession sess = SessionFactory.OpenSession())
            {
                IQuery qq = sess.CreateQuery("select p from TaskInstanceData as p where p.Status = :st").SetMaxResults(100);
                qq.SetInt32("st", TaskInstanceStatus.Ready);
                foreach (TaskInstanceData pd in qq.Enumerable<TaskInstanceData>())
                {
                    s.Add(pd.InstanceId);
                }
            }
            return s;
        }


        

     
        #endregion



        #region ITaskInstanceRepository Members

        public NGinnBPM.Runtime.TaskInstance GetTaskInstance(string instanceId)
        {
            DataObject dob = GetTaskState(instanceId);
            if (dob == null) return null;

            string definitionId = null, taskId = null, taskType = null;
            if (!dob.TryGet("ProcessDefinitionId", ref definitionId)) throw new Exception("Missing ProcessDefinitionId");
            if (!dob.TryGet("TaskId", ref taskId)) throw new Exception("Missing TaskId");
            dob.TryGet("__NG_TaskType", ref taskType);

            ProcessDefinition pd = PackageRepository.GetProcess(definitionId);
            Task tsk = pd.GetTask(taskId);
            if (tsk == null) throw new Exception("Task not found: " + taskId);

            TaskInstance ti = null;
            if (tsk == pd.Body)
            {
                ti = new ProcessInstance();
            }
            else if (tsk is CompositeTask)
            {
                ti = new CompositeTaskInstance();
            }
            else 
            {
                if (tsk.IsMultiInstance)
                {
                    if (taskType == null) throw new Exception("Missing __NG_TaskType");
                    if (taskType == "MultiTaskInstance")
                        ti = new MultiTaskInstance();
                    else 
                        ti = TaskFactory.CreateTaskInstance(tsk);
                }
                else
                    ti = TaskFactory.CreateTaskInstance(tsk);
            }
            ti.ParentProcess = pd; 
            INGinnPersistent pers = (INGinnPersistent)ti;
            pers.RestoreState(dob);
            
            return ti;
        }

        public void UpdateTaskInstance(NGinnBPM.Runtime.TaskInstance inst)
        {
            using (ISession ss = SessionFactory.OpenSession())
            {
                using (ITransaction tr = ss.BeginTransaction())
                {
                    TaskInstanceData tid = ss.Load<TaskInstanceData>(inst.InstanceId);
                    if (tid.RecordVersion != inst.PersistedVersion)
                    {
                        log.Warn("Process {0}: Persisted version is {1} and memory version is {2}", inst.InstanceId, tid.RecordVersion, inst.PersistedVersion);
                    }
                    tid.RecordVersion += 1;
                    inst.PersistedVersion = tid.RecordVersion;
                    inst.Passivate();
                    INGinnPersistent pers = (INGinnPersistent)inst;
                    DataObject dob = pers.SaveState();
                    dob["__NG_TaskType"] = inst.GetType().Name;
                    tid.TaskData = dob.ToXmlString("ProcessInstance");
                    tid.Status = ss.Load<TaskInstanceStatus>((int)inst.Status);
                    tid.LastModified = DateTime.Now;
                    tid.SchedulingStatus = inst.CanContinue ? TaskSchedulingStatus.Ready : TaskSchedulingStatus.Waiting;
                    tid.TaskType = inst.GetType().Name;
                    if (inst.FinishedDate.HasValue)
                        tid.FinishDate = inst.FinishedDate.Value;
                    if (inst.SelectedDate.HasValue)
                        tid.StartDate = inst.SelectedDate.Value;
                    tr.Commit();
                    
                    log.Debug("Updated task {0}: {1}", tid.InstanceId, tid.TaskData);
                }
            }
        }

        public void InsertTaskInstance(NGinnBPM.Runtime.TaskInstance inst)
        {
            inst.Passivate();
            INGinnPersistent pers = (INGinnPersistent)inst;
            DataObject dob = pers.SaveState();
            dob["__NG_TaskType"] = inst.GetType().Name;
            lock (this)
            {
                if (GetTaskInstance(inst.InstanceId) != null) throw new ApplicationException("Duplicate instance ID");
                using (ISession ss = SessionFactory.OpenSession())
                {
                    using (ITransaction tr = ss.BeginTransaction())
                    {
                        TaskInstanceData tid = new TaskInstanceData();
                        tid.InstanceId = inst.InstanceId;
                        tid.ProcessDefinitionId = inst.ProcessDefinitionId;
                        tid.ProcessInstanceId = inst.ProcessInstanceId;
                        tid.ParentTaskInstanceId = inst.ParentTaskInstanceId;
                        tid.StartDate = DateTime.Now;
                        tid.Status = ss.Load<TaskInstanceStatus>((int)inst.Status);
                        tid.RecordVersion = inst.PersistedVersion;
                        tid.RecordVersion = inst.PersistedVersion;
                        tid.LastModified = DateTime.Now;
                        tid.TaskData = dob.ToXmlString("TaskInstance");
                        tid.TaskType = inst.GetType().Name;
                        tid.TaskId = inst.TaskId;
                        tid.SchedulingStatus =  inst.CanContinue ? TaskSchedulingStatus.Ready : TaskSchedulingStatus.Waiting;
                        ss.Save(tid);
                        tr.Commit();
                    }
                }
            }
        }

        public IList<string> GetTaskInstancesReadyForProcessing(int maxNum)
        {
            List<string> s = new List<string>();
            using (ISession ss = SessionFactory.OpenSession())
            {
                IQuery qq = ss.CreateQuery("select t.InstanceId from TaskInstanceData as t where t.SchedulingStatus = :st").SetMaxResults(100);
                qq.SetEnum("st", TaskSchedulingStatus.Ready);
                foreach (string id in qq.Enumerable<string>())
                {
                    s.Add(id);
                }
            }
            return s;
        }

        public IDefaultTaskInstanceFactory TaskFactory { get; set; }
        
        private IProcessPackageRepository _pkgRepo;

        public IProcessPackageRepository PackageRepository
        {
            get { return _pkgRepo; }
            set { _pkgRepo = value; }
        }

        #endregion

        public void SetTaskInstanceErrorStatus(string instanceId, string errorInfo)
        {
            UpdateTaskSchedulingStatus(instanceId, TaskSchedulingStatus.Error, errorInfo);
        }

        public void UpdateTaskSchedulingStatus(string instanceId, TaskSchedulingStatus status, string additionalInfo)
        {
            using (ISession ss = SessionFactory.OpenSession())
            {
                using (ITransaction tr = ss.BeginTransaction())
                {
                    TaskInstanceData td = ss.Load<TaskInstanceData>(instanceId);
                    td.SchedulingStatus = status;
                    tr.Commit();
                }
            }
        }

        #region ITaskInstanceRepository Members


        public void SetWaitingTaskInstancesReady(string processDefinition, string taskId)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region ITaskInstanceRepository Members


        public IList<string> FindProcessesByCorrelationId(string processCorrelationId, string definitionId)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
